﻿using Padsurface.CoreAudio.Constants;
using Padsurface.CoreAudio.MultiMediaStructures;
using System;
using System.IO;

namespace Padsurface.Audio.Wave
{
    public static class WaveFileExtensions
    {
        public static BinaryWriter WriteHeader(this BinaryWriter binaryWriter, WaveFormatEx waveFormat, out int dataSizePosition)
        {
            if (binaryWriter == null) throw new ArgumentNullException("binaryWriter");
            if (waveFormat == null) throw new ArgumentNullException("waveFormat");

            binaryWriter.Seek(0, SeekOrigin.Begin);

            binaryWriter.WriteRiffChunk();
            binaryWriter.WriteFormatSubChunk(waveFormat);
            binaryWriter.WriteDataSubChunk(out dataSizePosition);

            return binaryWriter;
        }

        public static void UpdateHeader(this BinaryWriter binaryWriter, int dataSizePosition, int dataChunkSize)
        {
            if (binaryWriter == null) throw new ArgumentNullException("binaryWriter");
            if (dataSizePosition < 40) throw new ArgumentException("dataSizePosition cannot be smaller than 40, these bytes are reserved for other header details", "dataSizePosition");

            binaryWriter.UpdateRiffChunk();
            binaryWriter.UpdateDataSubChunk(dataSizePosition, dataChunkSize);
        }

        private static void WriteRiffChunk(this BinaryWriter binaryWriter)
        {
            binaryWriter.Write("RIFF".ToCharArray());    // Group id
            binaryWriter.Write((uint)0);                 // File length, total file length minus 8, which is taken up by RIFF
            binaryWriter.Write("WAVE".ToCharArray());    // Riff type
        }

        private static void WriteFormatSubChunk(this BinaryWriter binaryWriter, WaveFormatEx waveFormat)
        {
            binaryWriter.Write("fmt ".ToCharArray());

            uint samplesPerSecond = (uint)waveFormat.SamplesPerSec;
            ushort channels = (ushort)waveFormat.Channels;
            ushort bitsPerSample = (ushort)waveFormat.BitsPerSample;
            ushort blockAlign = (ushort)(channels * (bitsPerSample / 8));
            uint averageBytesPerSec = (samplesPerSecond * blockAlign);

            binaryWriter.Write((uint)(18 + waveFormat.ExtraSize));               // Length of header in bytes
            unchecked { binaryWriter.Write((short)0xFFFE); }                     // Format tag, 65534 (WAVE_FORMAT_EXTENSIBLE)
            binaryWriter.Write(channels);                                        // Number of channels
            binaryWriter.Write(samplesPerSecond);                                // Frequency of the audio in Hz... 44100
            binaryWriter.Write(averageBytesPerSec);                              // For estimating RAM allocation
            binaryWriter.Write(blockAlign);                                      // Sample frame size, in bytes
            binaryWriter.Write(bitsPerSample);

            binaryWriter.Write((short)waveFormat.ExtraSize);                     // Extra param size
            binaryWriter.Write(bitsPerSample);                                   // Should be valid bits per sample
            binaryWriter.Write((uint)3);                                         // Should be channel mask
            byte[] subformat = new Guid(KsMedia.WAVEFORMATEX).ToByteArray();
            binaryWriter.Write(subformat, 0, subformat.Length);
        }

        private static void WriteDataSubChunk(this BinaryWriter binaryWriter, out int dataSizePosition)
        {
            // Write the data chunk
            binaryWriter.Write("data".ToCharArray());                // Chunk id

            dataSizePosition = (int)binaryWriter.BaseStream.Position;
            binaryWriter.Write((uint)0);                             // Chunk size, length of header in bytes
        }

        private static void UpdateRiffChunk(this BinaryWriter binaryWriter)
        {
            binaryWriter.Seek(4, SeekOrigin.Begin);
            binaryWriter.Write((uint)(binaryWriter.BaseStream.Length - 8));    // File length
        }

        private static void UpdateDataSubChunk(this BinaryWriter binaryWriter, int dataSizePosition, int dataChunkSize)
        {
            binaryWriter.Seek((int)dataSizePosition, SeekOrigin.Begin);
            binaryWriter.Write((uint)dataChunkSize);
        }
    }
}
